package com.gitee.weixin.mp.handler.msg.text;

import com.gitee.weixin.mp.handler.msg.text.iface.MatchScore;
import com.gitee.weixin.mp.handler.msg.text.iface.TxTransaction;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;

/**
 * 文本交易的抽象类
 *
 * @author aaron 2017年12月29日
 */
public abstract class AbstractTxTransaction implements TxTransaction, MatchScore {
    public static final Logger logger = LoggerFactory.getLogger(AbstractTxTransaction.class);

    public static final String SESSION_KEY_TXTRANSACTION = "AbstractTxTransaction.SESSION_KEY_TXTRANSACTION";

    public static final double PERFECT_MATCH = 1.0;
    public static final double EQUALS_ICASE  = 0.9;
    public static final double START_WITH    = 0.8;
    public static final double END_WITH      = 0.7;
    public static final double REG_MATCH     = 0.5;
    public static final double CONTAINS      = 0.2;
    public static final double NOT_MATCH     = 0;


    /**
     * 是否为异步交易？<br>
     * true: 异步，doTextTrans 返回null<br>
     * false: 同步，doTextTrans返回WxMpXmlOutMessage
     */
    protected boolean asyn = true;
    /**
     * 如果为空表示不匹配任何文本
     */
    protected        List<String>  regexStr;
    private volatile List<Pattern> patterns;
    /**
     * 保证初始化只执行一次 , 但有可能未初始化完成时，被其他线程访问（返回NOT_MATCH），此风险可以忽略，不会产生实质性影响
     */
    private volatile AtomicBoolean compiled = new AtomicBoolean(false);

    @Override
    public double matchScore(String text) {
        // 初始化正则表达式
        if (!compiled.getAndSet(true) && patterns == null) {
            if (regexStr != null) {
                patterns = new ArrayList<>();
                regexStr.stream().filter(s -> StringUtils.isNotEmpty(s)).forEach(s -> patterns.add(Pattern.compile(s)));
            }
        }

        if (regexStr != null && patterns != null && text != null) {
            Optional<?> option = regexStr.stream().filter(s -> StringUtils.isNotEmpty(s)).filter(s -> s.equals(text)).findFirst();
            if (option.isPresent()) {
                return PERFECT_MATCH;
            } else {
                option = regexStr.stream().filter(s -> StringUtils.isNotEmpty(s)).filter(s -> s.equalsIgnoreCase(text)).findFirst();
                if (option.isPresent()) {
                    return EQUALS_ICASE;
                } else {
                    option = regexStr.stream().filter(s -> StringUtils.isNotEmpty(s)).filter(s -> text.startsWith(s)).findFirst();
                    if (option.isPresent()) {
                        return START_WITH;
                    } else {
                        option = regexStr.stream().filter(s -> StringUtils.isNotEmpty(s)).filter(s -> text.endsWith(s)).findFirst();
                        if (option.isPresent()) {
                            return END_WITH;
                        } else {
                            option = patterns.stream().filter(p -> p.matcher(text).matches()).findFirst();
                            if (option.isPresent()) {
                                return REG_MATCH;
                            } else {
                                option = regexStr.stream().filter(s -> StringUtils.isNotEmpty(s)).filter(s -> text.contains(s)).findFirst();
                                if (option.isPresent()) {
                                    return CONTAINS;
                                }
                            }
                        }
                    }
                }
            }
        }

        return NOT_MATCH;
    }

    /**
     * 设置交易的匹配规则，如果不设置，交易不会匹配任何消息
     *
     * @param regexStr
     */
    protected void setRegexStr(List<String> regexStr) {
        this.regexStr = regexStr;
    }

    /**
     * 是否为异步交易
     */
    public boolean isAsyn() {
        return asyn;
    }

    /**
     * 设置为异步交易
     */
    public void setAsyn(boolean asyn) {
        this.asyn = asyn;
    }
}
